/***************************************************************************
 *   Copyright (C) 2009 by Geoffrey McRae   *
 *   geoff@spacevs.com   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <pthread.h>

#include "mediaplayer.h"
#include "common/player.h"

__attribute__((constructor))
void init() {av_register_all();}

mp_t* mp_new() {
	mp_t* p;
	p		= calloc(sizeof(mp_t), 1);
	p->aStreams	= array_new();
	p->lEventsPTS	= ll_new(NULL, 0);
	p->lEventsMC	= ll_new(NULL, 0);
	p->lEventsAS	= ll_new(NULL, 0);

	pthread_rwlock_init(&p->mRunning, NULL);
	pthread_mutex_init (&p->mClock  , NULL);
	pthread_rwlock_init(&p->lClock	, NULL);
	pthread_cond_init  (&p->cClock  , NULL);
	pthread_rwlock_init(&p->lSeek   , NULL);
	return p;
}

void mp_free(mp_t *p) {
	int i;
	if (p->bLoaded) {
		WRN("%s", "File left open, closing before free");
		mp_close(p);
	}

	pthread_rwlock_destroy(&p->mRunning);
	pthread_mutex_destroy (&p->mClock  );
	pthread_rwlock_destroy(&p->lClock  );
	pthread_cond_destroy  (&p->cClock  );
	pthread_rwlock_destroy(&p->lSeek   );

	for(i = 0; i < array_count(p->aStreams); ++i)
		mp_stream_free(array_get(p->aStreams, i));
	array_free(p->aStreams);

	ll_free(p->lEventsPTS	);
	ll_free(p->lEventsMC	);
	ll_free(p->lEventsAS	);

	free(p);
}

mp_stream_t* mp_stream_new(mp_t *p, mp_renderer_t *renderer) {
	mp_stream_t *s;
	int		i;
	mp_queue_t	*q;

	s		= calloc(sizeof(mp_stream_t), 1);
	s->p		= p;

	s->lPackets	= ll_new(mp_av_free_packet, 100);
	pthread_rwlock_init(&s->lStop   , NULL);
	pthread_rwlock_init(&s->lRunning, NULL);

	/* setup the queue */
	s->rndr = renderer;
	s->iQueueSize = renderer->video_queue_size;
	if (s->iQueueSize > 0) {
		sem_init(&s->sQueue, 0, s->iQueueSize - 1);
		s->lQueue = ll_new(free, s->iQueueSize);
		for(i = 0; i < s->iQueueSize; i++) {
			q	= calloc(sizeof(mp_queue_t), 1);
			q->s	= s;
			ll_push(s->lQueue, q);			
		}
	}

	return s;
}

void mp_stream_free(void *_data) {
	mp_stream_t *s = _data;

	ll_free(s->lPackets);

	if (s->lQueue) {
		ll_free(s->lQueue);
		sem_destroy(&s->sQueue);
	}

	pthread_rwlock_destroy(&s->lRunning);
	pthread_rwlock_destroy(&s->lStop   );
	free(s);
}

bool mp_load(mp_t *p, char *filename) {
	assert(!p->bLoaded);
	int err;

	if ((err = av_open_input_file(&p->pFormatCtx, filename, NULL, 0, NULL)) != 0) {
		ERR("%d: Unable to open the input file: %s", err, filename);
		return false;
	}

	if ((err = av_find_stream_info(p->pFormatCtx)) < 0) {
		ERR("%d: Unable to find stream info", err);
		av_close_input_file(p->pFormatCtx);
		return false;
	}

	if (!(p->iNumStreams = p->pFormatCtx->nb_streams)) {
		ERR("%s", "There are no streams in the supplied file");
		av_close_input_file(p->pFormatCtx);
		return false;
	}

	p->bLoaded = true;
	return true;
}

unsigned int mp_num_streams(mp_t *p, unsigned int type) {
	unsigned int	i, ret;
	AVStream	*pStream;

	ret = 0;
	for(i = 0; i < p->iNumStreams; ++i) {
		pStream = p->pFormatCtx->streams[i];
		if (pStream->codec->codec_type == type)
			++ret;
	}

	return ret;
}

bool mp_setup_stream(mp_t *p, unsigned int index, mp_renderer_t *renderer, void* data) {
	assert(p->bLoaded	);
	assert(!p->bOpen	);

	unsigned int	i, cnt;
	AVStream	*pStream;
	mp_stream_t	*s;

	cnt = 0;
	for(i = 0; i < p->iNumStreams; ++i) {
		pStream = p->pFormatCtx->streams[i];
		if (pStream->codec->codec_type == renderer->iType) {
			if (cnt == index) {
				s		= mp_stream_new(p, renderer);
				s->iIndex	= i;
				s->pStream	= pStream;
				s->data		= data;
				s->rndr_data	= s->rndr->new(s->data);
				array_push(p->aStreams, s);
				return true;
			}
			++cnt;
		}
	}

	return false;
}

bool mp_open(mp_t *p) {
	assert(p->bLoaded	);
	assert(!p->bOpen	);

	int		i;
	mp_stream_t *s;
	AVCodec		*c;

	/* Open any and all appropriate codecs */
	for(i = array_count(p->aStreams) - 1; i >= 0; --i) {
		s = array_get(p->aStreams, i);

		/* Find the decoder */
		c = avcodec_find_decoder(s->pStream->codec->codec_id);
		if (!c) {
			ERR("Unable to find a suitable decoder for stream %d (codec id %d)", s->iIndex, s->pStream->codec->codec_id);
			mp_stream_free(s);
			array_del(p->aStreams, i, 1);
			continue;
		}

		/* Open the decoder */
		if (avcodec_open(s->pStream->codec, c) < 0) {
			ERR("Unable to open the decoder for stream %d", s->iIndex);
			mp_stream_free(s);
			array_del(p->aStreams, i, 1);
			continue;
		}

		INF("Opened codec \"%s\" for stream %d", c->name, s->iIndex);
		INF("Using renderer \"%s\" for stream %d", s->rndr->cName, s->iIndex);
	}
	
	
	p->bOpen = true;
	return true;
}

void mp_close(mp_t *p) {
	if (!p->bOpen) return;

	int		i;
	mp_stream_t	*s;

	if (p->bRunning) {
		WRN("%s", "Demuxer running, stopping first");
		mp_stop(p);
	}

	/* Free the renderers and streams */
	for(i = 0; i < array_count(p->aStreams); ++i) {
		s = array_get(p->aStreams, i);
		s->rndr->free(s->rndr_data);
		mp_stream_free(s);
	}
	array_empty(p->aStreams);

	av_close_input_file(p->pFormatCtx);
	p->bOpen	= false;
	p->bLoaded	= false;
}

void mp_play(mp_t *p) {
	assert(p->bOpen);
	assert(!p->bRunning);

	p->bRunning = true;
	pthread_create(&p->tDemuxer, NULL, mp_thread_demuxer, p);
}

void mp_pause(mp_t *p) {
	assert(p->bOpen);
	assert(p->bRunning);

	pthread_rwlock_wrlock(&p->lClock);
	p->bPaused = !p->bPaused;
	pthread_rwlock_unlock(&p->lClock);
}

bool mp_is_playing(mp_t *p) {
	return p->bRunning;
}

void mp_stop(mp_t *p) {
	int	i;
	ll_t	*ll;

	assert(p->bRunning);
	pthread_rwlock_wrlock(&p->mRunning);
	p->bRunning = false;
	pthread_rwlock_unlock(&p->mRunning);

	/* Empty the packet queues */
	for(i = 0; i < array_count(p->aStreams); ++i) {
		ll = ((mp_stream_t*)array_get(p->aStreams, i))->lPackets;
		ll_empty(ll);
		ll_drain(ll);
	}

	/* Wait for the demuxer to stop */
	pthread_join(p->tDemuxer, NULL);

	/* plug them up again */
	for(i = 0; i < array_count(p->aStreams); ++i) {
		ll = ((mp_stream_t*)array_get(p->aStreams, i))->lPackets;
		ll_plug(ll);
	}
}

void mp_add_event(mp_t *p, enum mp_event_type type, double pts, mp_func_event fn_event, void* data) {
	mp_event_t	*event;
	event		= malloc(sizeof(mp_event_t));
	event->pts	= pts;
	event->fn_event	= fn_event;
	event->data	= data;

	switch(type) {
		case MP_EVENT_PTS_VALUE		: ll_push(p->lEventsPTS	, event); break;
		case MP_EVENT_MASTER_CLOCK_TICK	: ll_push(p->lEventsMC	, event); break;
		case MP_EVENT_AUDIO_SYNC	: ll_push(p->lEventsAS	, event); break;
	}
}

void mp_seek(mp_t *p, double pts) {
	assert(p->bRunning);
	pthread_rwlock_wrlock(&p->lSeek);
	p->bSeek = true;
	p->dSeek = pts;
	pthread_rwlock_unlock(&p->lSeek);
}

void mp_wait(mp_t *p) {
	assert(p->bRunning);
	pthread_join(p->tDemuxer, NULL);
}
